/**
 * Top ten player lists - overall and for each guild.
 *<p>
 * archive and top_ten is of the format:
 *<code><p>
 *       ({ ({ name (guild), rating, level, age }), ... })
 *</code><p>
 * guild_top_tens is of the format:
 *<code><p>
 *       ([ guild: ({ ({ name, rating, level, age }), ... }), ... ])
 * </code>
 * @author Deutha
 */

#include <skills.h>
#include <top_ten_tables.h>
#include <clubs.h>

#define SAVE_FILE "/save/top_ten_tables"
#define TOP_TEN_SIZE 15
#define GUILD_TOP_TEN_SIZE 10
#define ARCHIVE_TIME 50 * 24 * 60 * 60
#define ARCHIVE_RATING 30000

/* The Apex Club accepts members with a rating of 10k or greater */
#define APEX_LIMIT 10000

/* Age in days = age in seconds / 86400 */
#define AGE_DIVIDER 100
#define AGE ( -thing->query_time_on() / 86400 )

mapping guild_top_tens;
mixed *archive, *top_ten;
nosave int average;
nosave mixed *skills;
string _family_largest;
int _family_largest_num;
string _family_qp;
int _family_qp_num;
string _family_age;
int _family_age_num;
mapping _family_guilds;
mapping _family_guilds_num;
string _family_pk;
int _family_pk_num;
string _family_single_gender;
int _family_single_gender_num;
string _family_start_time;
int _family_start_time_num;
string _family_relationships;
int _family_relationships_num;

string _club_largest;
int _club_largest_num;
string _club_qp;
int _club_qp_num;
string _club_age;
int _club_age_num;
mapping _club_guilds;
mapping _club_guilds_num;
string _club_pk;
int _club_pk_num;
string _club_single_gender;
int _club_single_gender_num;
string _club_start_time;
int _club_start_time_num;

void calculate_family_and_club_info();

void create() {
   seteuid( (string)"/secure/master"->
         creator_file( file_name( this_object() ) ) );
   guild_top_tens = ([ ]);
   _family_guilds = ([ ]);
   _club_guilds = ([ ]);
   _family_guilds_num = ([ ]);
   _club_guilds_num = ([ ]);
   archive = ({ });
   top_ten = ({ });
   call_out( "check_tables", 5 );
   call_out( "check_family_and_club_info", 10 );
   if ( file_size( SAVE_FILE +".o" ) > 0 ) {
      unguarded( (: restore_object, SAVE_FILE :) );
      if (!_family_guilds_num) {
         _family_guilds_num = ([ ]);
      }
      if (!_club_guilds_num) {
         _club_guilds_num = ([ ]);
      }
   }
} /* setup() */

private void save_me() {
   unguarded( (: save_object, SAVE_FILE :) );
} /* save_me() */

/**
 * Some sort of weight average used in the rating calulation.
 * @return the weighted average
 */
int query_average() { return average; }
/**
 * The skils list with weights.  This is used to help generate the
 * rating.
 * @return the skils list with ratings
 */
mixed *query_skills() { return skills; }

/**
 * The mapping which is the guild top ten tables.
 * @return the guild top ten mapping
 */
mapping query_guild_top_tens() { return guild_top_tens; }

/** @ignore yes */
void set_guild_top_tens( mapping map ) {
   guild_top_tens = map;
   save_me();
} /* set_guild_top_tens() */

/**
 * Add in a new table.  This should be used when a new guild is created
 * and their table needs to be defined.
 * @param word the name of the table to create
 */
void add_guild_top_ten( string word ) {
   if ( guild_top_tens[ word ] ) {
      return;
   }
   guild_top_tens[ word ] = ({ });
   save_me();
} /* add_guild_top_ten() */

/**
 * The complete archived top ten table.
 * @return the archived top ten table
 */
mixed *query_archive() { return archive; }

/** @ignore yes */
void set_archive( mixed *args ) {
   archive = args;
   save_me();
} /* set_archive() */

/**
 * Return the complete list of top ten tables.
 * @return the array of arrays being the top ten tab;les
 */
mixed *query_top_ten() { return top_ten; }

/** @ignore yes */
void set_top_ten( mixed *args ) {
   top_ten = args;
   save_me();
} /* set_top_ten() */

/** @ignore yes */
int check_person( mixed *args, string guild ) {
   int i;
   string word;

   if ( !guild ) {
      sscanf( args[ 0 ], "%s (%s)", word, guild );
   } else {
      word = args[ 0 ];
   }

   if ( !rank( word ) ||
        !PLAYER_H->test_active( word ) ||
        creatorp( word ) ||
        PLAYER_H->test_property( word, "no_score" ) ||
        PLAYER_H->test_property( word, "no score" ) ) {
     return 0;
   }

   if ( ( (int)PLAYER_H->test_last( word ) < time() - ARCHIVE_TIME ) ) {
      if ( args[ 1 ] > ARCHIVE_RATING ) {
         args[ 0 ] = word +" ("+ guild +")";
         for ( i = sizeof( archive ) - 1; i > -1; i-- ) {
            if ( archive[ i ][ 0 ] == args[ 0 ] ) {
               if ( archive[ i ][ 1 ] > args[ 1 ] )
                  archive[ i ] = args + ({ time() });
               break;
            }
         }
         if ( i == -1 ) {
            archive += ({ args + ({ time() }) });
         }
      }
      return 0;
   }
   return 1;
} /* check_person() */

/**
 * Wander over the top ten tables checking to see if everyone
 * still exists.
 */
void check_tables() {
   int i;
   string word;

   for ( i = sizeof( top_ten ) - 1; i > -1; i-- ) {
      if ( !check_person( copy( top_ten[ i ] ), 0 ) ) {
         top_ten = delete( top_ten, i, 1 );
      }
   }
   foreach ( word in keys( guild_top_tens ) ) {
      for ( i = sizeof( guild_top_tens[ word ] ) - 1; i > -1; i-- ) {
         if ( !check_person( copy( guild_top_tens[ word ][ i ] ), word ) ) {
            guild_top_tens[ word ] = delete( guild_top_tens[ word ], i, 1 );
         }
      }
   }
   save_me();
} /* check_tables() */

/**
 * Returns the ordered list of people on the top ten list.  If the table
 * name is 0 or it is "main" the main table is checked.  If the
 * table name is "archive" then the archive is used.
 * @param table_name the name of the tanble to check
 * @return the array of arrays of the top ten information
 * @see /include/top_ten_tables.h
 */
mixed *query_ordered_table( string table_name ) {
   int i, highest_loc, highest_num;
   mixed *ret, *args;

   if ( !table_name || table_name == "main") {
      args = top_ten;
   } else {
      if ( table_name == "archive" ) {
         args = archive;
      } else {
         if ( !guild_top_tens[ table_name ] ) {
            return ({ });
         }
         args = guild_top_tens[ table_name ];
      }
   }
   ret = ({ });
   while ( sizeof( args ) ) {
      highest_loc = highest_num = 0;
      for ( i = 0; i < sizeof( args ); i++ ) {
         if ( args[ i ][ TOP_TEN_RATING ] > highest_num ) {
            highest_num = args[ i ][ TOP_TEN_RATING ];
            highest_loc = i;
         }
      }
      ret += ({ args[ highest_loc] });
      args = args[ 0 .. highest_loc - 1 ] + args[ highest_loc + 1 .. ];
   }
   return ret;
} /* query_orderd_table() */

/** @ignore yes */
mixed *remove_name( string word, mixed *args ) {
   int i;

   if ( !sizeof( args ) ) {
      return ({ });
   }
   for ( i = sizeof( args ) - 1; i > -1; i-- ) {
      if ( word == explode( args[ i ][ TOP_TEN_NAME ], " " )[ 0 ] ) {
         args = delete( args, i, 1 );
      }
   }
   return args;
} /* remove_name() */

/** @ignore yes */
int *find_lowest( mixed *args ) {
   int i, lowest_loc, lowest_num;

   if ( !sizeof( args ) ) {
      return ({ 0, 0 });
   }
   lowest_loc = 0;
   lowest_num = args[ 0 ][ TOP_TEN_RATING ];
   for ( i = 1; i < sizeof( args ); i++ ) {
      if ( lowest_num > args[ i ][ TOP_TEN_RATING ] ) {
         lowest_loc = i;
         lowest_num = args[ i ][ TOP_TEN_RATING ];
      }
   }
   return ({ lowest_loc, lowest_num });
} /* find_lowest() */

/** @ignore yes */
int query_skill_weight( string skill ) {
   int total;
   string *next;

   next = SKILL_H->query_immediate_children( skill );
   if ( !sizeof( next ) ) {
      return 1;
   }
   foreach ( skill in next ) {
      total += query_skill_weight( skill );
   }
   return total;
} /* query_skill_weight() */

/**
 * Figure out the rating for the player.
 * @param thing the object to get the ratingof
 * @return the current rating
 */
int calculate_rating( object thing ) {
   int i, j, rating, *bonuses;

   rating = (int)thing->query_level();
   if ( !skills ) {
      skills = ({ });
      for ( i = 0; i < sizeof( STD_SKILLS ); i += SKILL_ARR_SIZE ) {
         skills += ({ SKILL_H->query_immediate_children( STD_SKILLS[ i ] )
         });
         skills += ({
               sizeof( skills[ <1 ] ),
               query_skill_weight( STD_SKILLS[ i ] )
         });
         average += skills[ <1 ];
      }
      average /= sizeof( skills ) / 3;
   }
   bonuses = allocate( sizeof( skills ) / 3 );
   for ( i = sizeof( bonuses ) - 1; i > -1; i-- ) {
      for ( j = skills[ 3 * i + 1 ] - 3; j > -1; j-- ) {
         bonuses[ i ] +=
               (int)thing->query_skill_bonus( skills[ 3 * i ][ j ], 1 );
      }
      bonuses[ i ] = ( ( bonuses[ i ] / skills[ 3 * i + 1 ] ) *
            skills[ 3 * i + 2 ] ) / average;
   }
   bonuses = sort_array( bonuses, -1 );
   j = sizeof( bonuses );
   for ( i = 0; i < j; i++ ) {
      rating = 2 * rating + bonuses[ i ];
      if ( !i && userp( thing ) ) {
         rating += (int)LIBRARY_H->
               query_quest_points( (string)thing->query_name() );
      }
   }

   if( rating > APEX_LIMIT && userp( thing ) ) {
      rating = AGE * (rating - APEX_LIMIT) / AGE_DIVIDER + APEX_LIMIT;
   }

   return rating;
} /* calculate_rating() */

/**
 * Called when a player advances their skills.
 * @param word the skill being advanced
 * @param thing the player which advanced their skils
 */
varargs void player_skill_advance( mixed word, object thing ) {
   int rating, *lowest;
   string name;
   mixed *guild_top_ten;

   if( !thing && objectp(word) ) {
       thing = word;
       word = "I'm broken";
    }

   if ( creatorp(thing) ) {
      return;
   }
   if ( thing->query_property( "guest" ) ) {
      return;
   }
   if ( thing->query_property( "no_score" ) ) {
      return;
   }
   if ( thing->query_property( "no score" ) ) {
      return;
   }
   guild_top_ten = guild_top_tens[ word ];
   if ( !guild_top_ten ) {
      return;
   }
   name = (string)thing->query_name();
   rating = calculate_rating( thing );
   guild_top_ten = remove_name( name, guild_top_ten );
   lowest = find_lowest( guild_top_ten );
   if ( sizeof( guild_top_ten ) < GUILD_TOP_TEN_SIZE ) {
      guild_top_ten += ({
            ({ name, rating, (int)thing->query_level(),
            -(int)thing->query_time_on() }) });
   } else {
      if ( rating > lowest[ 1 ] ) {
         guild_top_ten[ lowest[ 0 ] ] =
               ({ name, rating, (int)thing->query_level(),
               -(int)thing->query_time_on() });
      }
   }
   guild_top_tens[ word ] = guild_top_ten;
   //   save_me();
   top_ten = remove_name( name, top_ten );
   lowest = find_lowest( top_ten );
   if ( sizeof( top_ten ) < TOP_TEN_SIZE ) {
      top_ten += ({
            ({ name +" ("+ word +")", rating,
            (int)thing->query_level(), -(int)thing->query_time_on() }) });
   } else {
      if ( rating > lowest[ 1 ] ) {
         top_ten[ lowest[ 0 ] ] =
               ({ name +" ("+ word +")", rating,
               (int)thing->query_level(), -(int)thing->query_time_on() });
      }
   }
   //   save_me();
} /* player_skill_advance() */

/**
 * Remove the named player from the named table.
 * @param word1 the name of the player
 * @param word2 the name of the table, 0 for the main table
 */
varargs void excise_name( string word1, string word2 ) {
   if ( !word2 ) {
      top_ten = remove_name( word1, top_ten );
   } else {
      if ( guild_top_tens[ word2 ] ) {
         guild_top_tens[ word2 ] = remove_name( word1,
               guild_top_tens[ word2 ] );
      }
   }
   save_me();
} /* excise_name() */

/**
 * Stuff to make sure that all the clubs still exist.
 */
void check_family_and_club_info() {
   int offset;
   string bing;
   string guild;

   _family_qp_num = 0;
   _family_age_num = 0;
   _family_largest_num = 0;
   if (!mapp(_family_guilds)) {
      _family_guilds = ([ ]);
   }

   if (!mapp(_club_guilds)) {
      _club_guilds = ([ ]);
   }

   // Do these first so that the table is not stuffed up.
   if (_family_qp && !CLUB_HANDLER->is_family(_family_qp)) {
      _family_qp = 0;
      _family_qp_num = 0;
   }
   if (_family_age && !CLUB_HANDLER->is_family(_family_age)) {
      _family_age = 0;
      _family_age_num = 0;
   }
   if (_family_largest && !CLUB_HANDLER->is_family(_family_largest)) {
      _family_largest = 0;
      _family_largest_num = 0;
   }
   if (_family_single_gender && !CLUB_HANDLER->is_family(_family_single_gender)) {
      _family_single_gender = 0;
      _family_single_gender_num = 0;
   }
   if (_family_pk && !CLUB_HANDLER->is_family(_family_pk)) {
      _family_pk = 0;
      _family_pk_num = 0;
   }

   if (_club_qp && !CLUB_HANDLER->is_club(_club_qp)) {
      _club_qp = 0;
      _club_qp_num = 0;
   }
   if (_club_age && !CLUB_HANDLER->is_club(_club_age)) {
      _club_age = 0;
      _club_age_num = 0;
   }
   if (_club_largest && !CLUB_HANDLER->is_club(_club_largest)) {
      _club_largest = 0;
      _club_largest_num = 0;
   }
   if (_club_single_gender && !CLUB_HANDLER->is_club(_club_single_gender)) {
      _club_single_gender = 0;
      _club_single_gender_num = 0;
   }
   if (_club_pk && !CLUB_HANDLER->is_club(_club_pk)) {
      _club_pk = 0;
      _club_pk_num = 0;
   }

   offset = 3;
   foreach (guild, bing in _family_guilds) {
      if (stringp(bing)) {
         if (!CLUB_HANDLER->is_family(bing)) {
            map_delete(_family_guilds, guild);
            map_delete(_family_guilds_num, guild);
         }
      } else {
         map_delete(_family_guilds, guild);
         map_delete(_family_guilds_num, guild);
      }
   }

   foreach (guild, bing in _club_guilds) {
      if (stringp(bing)) {
         if (!CLUB_HANDLER->is_club(bing)) {
            map_delete(_club_guilds, guild);
            map_delete(_club_guilds_num, guild);
         }
      } else {
         map_delete(_club_guilds, guild);
         map_delete(_club_guilds_num, guild);
      }
   }
   save_me();
} /* calculate_family_and_club_info() */

void inform_of_club(string club,
                    int family,
                    int type,
                    mixed num) {
   string str;
   int bing;

   if (family) {
      switch (type) {
      case TOP_TEN_LARGEST_FAMILY:
         if (_family_largest_num < num ||
             _family_largest == club) {
            _family_largest_num = num;
            _family_largest = club;
            save_me();
         }
         break;
      case TOP_TEN_OLDEST_FAMILY:
         if (_family_age_num < num ||
             _family_age == club) {
            _family_age_num = num;
            _family_age = club;
            save_me();
         }
         break;
      case TOP_TEN_MOST_QUEST_POINTS:
         if (_family_qp_num < num ||
             _family_qp == club) {
            _family_qp_num = num;
            _family_qp = club;
            save_me();
         }
         break;
      case TOP_TEN_MOST_GUILD:
         foreach (str, bing in num) {
            if (_family_guilds_num[str] < bing ||
                _family_guilds[str] == club) {
               _family_guilds_num[str] = bing;
               _family_guilds[str] = club;
               save_me();
            }
         }
         break;
      case TOP_TEN_MOST_PKS:
         if (_family_pk_num < num ||
             _family_pk == club) {
            _family_pk_num = num;
            _family_pk = club;
            save_me();
         }
         break;
      case TOP_TEN_SINGLE_GENDER:
         if (_family_single_gender_num < num ||
             _family_single_gender == club) {
            _family_single_gender_num = num;
            _family_single_gender = club;
            save_me();
         }
         break;
      case TOP_TEN_OLDEST_LOGONS:
         if (_family_start_time_num < num ||
             _family_start_time == club) {
            _family_start_time_num = num;
            _family_start_time = club;
            save_me();
         }
         break;
      case TOP_TEN_MOST_RELATIONSHIPS:
         if (_family_relationships_num < num ||
             _family_relationships == club) {
            _family_relationships_num = num;
            _family_relationships = club;
            save_me();
         }
         break;
      }
   } else {
      switch (type) {
      case TOP_TEN_LARGEST_FAMILY:
         if (_club_largest_num < num ||
             _club_largest == club) {
            _club_largest_num = num;
            _club_largest = club;
            save_me();
         }
         break;
      case TOP_TEN_OLDEST_FAMILY:
         if (_club_age_num < num ||
             _club_age == club) {
            _club_age_num = num;
            _club_age = club;
            save_me();
         }
         break;
      case TOP_TEN_MOST_QUEST_POINTS:
         if (_club_qp_num < num ||
             _club_qp == club) {
            _club_qp_num = num;
            _club_qp = club;
            save_me();
         }
         break;
      case TOP_TEN_MOST_GUILD:
         foreach (str, bing in num) {
            if (_club_guilds_num[str] < bing ||
                _club_guilds[str] == club ||
                !_club_guilds[str]) {
               _club_guilds_num[str] = bing;
               _club_guilds[str] = club;
               save_me();
            }
         }
         break;
      case TOP_TEN_MOST_PKS:
         if (_club_pk_num < num ||
             _club_pk == club) {
            _club_pk_num = num;
            _club_pk = club;
            save_me();
         }
         break;
      case TOP_TEN_SINGLE_GENDER:
         if (_club_single_gender_num < num ||
             _club_single_gender == club) {
            _club_single_gender_num = num;
            _club_single_gender = club;
            save_me();
         }
         break;
      case TOP_TEN_OLDEST_LOGONS:
         if (_club_start_time_num < num ||
             _club_start_time == club) {
            _club_start_time_num = num;
            _club_start_time = club;
            save_me();
         }
         break;
      }
   }
} /* inform_of_club() */

mixed *query_family_info() {
   return ({ _family_largest,
             _family_age,
             _family_qp,
             _family_guilds,
             _family_pk,
             _family_single_gender,
             _family_start_time,
             _family_relationships });
} /* query_family_info() */

mixed *query_club_info() {
   return ({ _club_largest,
             _club_age,
             _club_qp,
             _club_guilds,
             _club_pk,
             _club_single_gender,
             _club_start_time });
} /* query_club_info() */

/** @ignore yes */
void dest_me() {
  save_me();
  destruct(this_object());
} /* dest_me() */

/** @ignore yes */
void reset() {
  save_me();
} /* reset() */
